/*!
 * imageSet - Image Collection Management
 * 
 * Copyright (c) 2009 Eric Garside (http://eric.garside.name)
 * Dual licensed under:
 * 	MIT: http://www.opensource.org/licenses/mit-license.php
 *	GPLv3: http://www.opensource.org/licenses/gpl-3.0.html
 */
(function($){
	
	// Utility functions
	String.prototype.cleanId = function(){ return this.replace(/[^a-zA-Z0-9]+/g,'') }
	String.prototype.doEscape = function( i ){ return i ? escape(this) : this }
	function defined(x){ return x !== undefined && x !== null }
	
	/**
	 * ImageSet needs to be an instanciated class to allow for multiple simultaneous instances.
	 * The makeClass function by John Resig [Provided under the MIT License]
	 */
	if (!window.makeClass)	// makeClass - By John Resig (MIT Licensed)
		window.makeClass = function(){ return function (args){ if ( this instanceof arguments.callee ) this.init.apply( this, args.callee ? args : arguments ); else return new arguments.callee( arguments ) }}
	
	/** Plugin Options **/
	
	$.imageSet = {
		defaults: {
			// Should we escape the URLs?
			escape: true,
			// The common path (with trailing slash) which all images in the set share [/path/to/]image.jpg
			path: '',
			// The size of each imageSet chunk
			chunkSize: 25,
			// Should we maintain a reference to the image data?
			keepReferences: false,
			// Should we preload the images?
			preload: true,
			// Where should we start preloading?
			preloaderAt: 'cookie',
			// A flat array of images
			data: [],
			// Additional Info for images
			metadata: {},
			// Custom references to the images (if enabled)
			ref: {},
			// Backward-Lookup for Reference by Array Position
			refByPos: [],
			// The number of chunks in the image set
			chunks: 0,
			// The current chunk in the image set
			chunk: 0
		}
	}
	
		// Default options for jCookie
	var cookieOpts = {path: '/'};
		
	/** Event Delegation **/
	
	$('.ui-imageset-delegate')
		.live('load', function(){
			var el = $(this),
				self = el.data('self'),
				imgIndex = self.preloaderAt;
			
			// If we have preloading to do, increment the preloader and begin loading.
			if (!self.preload) return false;
			
			// If we've already loaded all images, trigger the delegate's preload function
			if (self.preloaderAt >= self.length) return self.delegate.trigger('preloaded', [self.preloaderAt]);
			
			// Otherwise, move on to the next image, and continue preloading
			self.preloaderAt++;
			
			// If jCookie is around, utilize it to store group preloading info
			if ($.cookie) $.cookie(self.cookie, self.preloaderAt, cookieOpts);
			
			// If we have more images to load, call this function again
			el.attr('src', self.get(imgIndex));
		})
		.live('ready', function(){
			$(this).data('self').isReady = true;
		})
	
	
	/** jQuery Entry Point **/
	
	var imageSet = $.Image = makeClass();
	
	$.extend(imageSet.prototype, {
		/** Object Core **/
		init: function(name, options){
			// Set up the object
			var self = this;
			options = $.extend({title: name, cookie: 'jIMGPreload_' + escape(name)}, $.imageSet.defaults, options);
			
			$.each(options, function(k,v){ self[k] = v })
			
			this.isReady = false;
			this.clear();

			// Set up the event delegate/preloader
			this.delegate = $('<img class=".ui-imageset-delegate"/>').data('self', this);
			
			// Check and see if we need to store the preloaderAt value from a cookie
			if (this.preloaderAt == 'cookie')
				this.preloaderAt = $.cookie ? $.cookie(this.cookie)||0 : 0;
		},
		// Allow binding and auto-execution for onReady callbacks
		ready: function(f){
			if (typeof f != 'function') return this;

			if (this.isReady) f.call(this.delegate);
			else this.delegate.bind('ready', f);
			return this;
		},
		// Index the data array, trigger the onReady event
		index: function(){
			this.length = this.data.length;
			this.chunks = Math.ceil( this.length/this.chunkSize ); 
			this.delegate.trigger('load');
			if (!this.isReady) this.delegate.trigger('ready', [this]);
			return this;
		},
		
		/** Preloading Management **/
		enablePreload: function(){ if (!this.preload){ this.preload = true; this.delegate.trigger('load') } return this },
		disablePreload: function(){ if (this.preload) this.preload = false; return this },
		
		/** Populate/Organize Chunks **/
		toChunk: function(x){ this.chunk = x < 0 ? 0 : x > this.chunks ? this.chunks : x; return this },
		nextChunk: function(){ return this.toChunk(this.chunk+1) },
		prevChunk: function(){ return this.toChunk(this.chunk-1) },
		current: function(){ var cs = this.chunkSize*1, s = this.chunk*1 *cs; return this.data.slice(s, s+cs) },
		
		/** Pagination Shortcuts **/
		first: function(){ return this.toChunk(0).current() },
		prev: function(){ return this.prevChunk().current() },
		next: function(){ return this.nextChunk().current() },
		last: function(){ return this.toChunk(this.chunks).current() },
		
		/** Image Access **/
		get: function(i){ return this.data[i]||null },
		byId: function(id){ id = id.cleanId(); return this.keepReferences ? defined(this.ref[id]) ? this.data[this.ref[id]]||null:null:null },
		meta: function(i){ return this.metadata[i]||null },
		metaById: function(id){ id = id.cleanId(); return this.keepReferences ? defined(this.ref[id]) ? this.metadata[this.ref[id]]||null:null:null },
		keyByIndex: function(i){ return this.refByPos[i]||null },
		image: function(i){ return {key: this.keyByIndex(i), index: i, url: this.get(i), meta: this.meta(i)} },
		
		/** Image Management **/
		// Add a single image. Users should wrap this in a custom data-seeding function
		add: function(url, id, meta){
			var i = this.data.push(this.path+url.doEscape( this.escape ))-1; 
			id = id.cleanId();
			
			if (this.keepReferences && id) {
				this.refByPos[i] = id;
				this.ref[id] = i;
			}
			
			if (meta)
				this.metadata[i] = meta;
			
			return this;
		},
		remove: function(id){
			var key, self = this;
			id = id.cleanId();
			
			if (this.keepReferences)
				if (defined(this.ref[id])){
					key = this.ref[id];
					
					delete this.ref[id];
					this.refByPos.splice(key, 1);

					// Shift the data in the datasets
					$.each(this.ref, function(id){
						if (this<key) return;
						var position = this-1;
						self.ref[id] = position;
						self.refByPos[position] = id;
					})
				} else key = id;
			else key = id;
			
			// Splice out both image and metadata
			this.data.splice(key, 1);
			this.metadata.splice(key, 1);
			
			this.index();	// Must re-index the set
			return this;
		},
		clear: function(){
			this.data = [];
			this.ref = {};
			this.refByPos = [];
			this.metadata = [];
			return this;
		}
	})
	
})(jQuery);
